/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "plugins/ecmascript/runtime/base/builtins_base.h"
#include "plugins/ecmascript/runtime/ecma_string.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/jobs/micro_job_queue.h"
#include "plugins/ecmascript/runtime/js_array.h"
#include "plugins/ecmascript/runtime/js_promise.h"
#include "plugins/ecmascript/runtime/js_tagged_value-inl.h"
#include "plugins/ecmascript/runtime/js_tagged_value.h"
#include "plugins/ecmascript/runtime/js_thread.h"
#include "plugins/ecmascript/runtime/object_factory.h"
#include "plugins/ecmascript/tests/runtime/common/test_helper.h"
#include "utils/bit_utils.h"

// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript;
// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript::builtins;

// NOLINTBEGIN(readability-magic-numbers)

namespace ark::test {
using JSArray = ark::ecmascript::JSArray;

class BuiltinsPromiseTest : public testing::Test {
public:
    static void SetUpTestCase()
    {
        GTEST_LOG_(INFO) << "SetUpTestCase";
    }

    static void TearDownTestCase()
    {
        GTEST_LOG_(INFO) << "TearDownCase";
    }

    void SetUp() override
    {
        TestHelper::CreateEcmaVMWithScope(instance_, thread_, scope_);
    }

    void TearDown() override
    {
        TestHelper::DestroyEcmaVMWithScope(instance_, scope_);
    }

protected:
    // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
    JSThread *thread_ {nullptr};
    // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
    PandaVM *instance_ {nullptr};

private:
    EcmaHandleScope *scope_ {nullptr};
};

// native function for race2 then_on_rejected()
JSTaggedValue TestPromiseRaceThenOnRejectd(EcmaRuntimeCallInfo *argv)
{
    JSHandle<JSTaggedValue> result = builtins_common::GetCallArg(argv, 0);
    // 12345 : test case
    EXPECT_EQ(JSTaggedValue::SameValue(result.GetTaggedValue(), JSTaggedValue(12345)), true);
    return JSTaggedValue::Undefined();
}

// native function for all then_on_resolved()
JSTaggedValue TestPromiseAllThenOnResolved(EcmaRuntimeCallInfo *argv)
{
    [[maybe_unused]] EcmaHandleScope handleScope(argv->GetThread());
    JSHandle<JSTaggedValue> array = builtins_common::GetCallArg(argv, 0);
    JSHandle<JSObject> objectArray = JSHandle<JSObject>::Cast(array);
    [[maybe_unused]] PropertyDescriptor desc(argv->GetThread());
    [[maybe_unused]] bool result1 = JSObject::GetOwnProperty(
        argv->GetThread(), objectArray, JSHandle<JSTaggedValue>(argv->GetThread(), JSTaggedValue(0)), desc);
    EXPECT_TRUE(result1);
    JSHandle<JSTaggedValue> value1 = desc.GetValue();
    // 111 : test case
    EXPECT_EQ(JSTaggedValue::SameValue(value1.GetTaggedValue(), JSTaggedValue(111)), true);
    [[maybe_unused]] bool result2 = JSObject::GetOwnProperty(
        argv->GetThread(), objectArray, JSHandle<JSTaggedValue>(argv->GetThread(), JSTaggedValue(1)), desc);
    EXPECT_TRUE(result2);
    JSHandle<JSTaggedValue> value2 = desc.GetValue();
    // 222 : test case
    EXPECT_EQ(JSTaggedValue::SameValue(value2.GetTaggedValue(), JSTaggedValue(222)), true);
    return JSTaggedValue::Undefined();
}

// native function for catch catch_on_rejected()
JSTaggedValue TestPromiseCatch(EcmaRuntimeCallInfo *argv)
{
    JSHandle<JSTaggedValue> result = builtins_common::GetCallArg(argv, 0);
    // 3 : test case
    EXPECT_EQ(JSTaggedValue::SameValue(result.GetTaggedValue(), JSTaggedValue(3)), true);
    return JSTaggedValue::Undefined();
}

// native function for then then_on_resolved()
JSTaggedValue TestPromiseThenOnResolved(EcmaRuntimeCallInfo *argv)
{
    [[maybe_unused]] EcmaHandleScope handleScope(argv->GetThread());
    auto factory = argv->GetThread()->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> result = builtins_common::GetCallArg(argv, 0);
    auto expect = factory->NewFromCanBeCompressString("resolve");
    EXPECT_EQ(JSTaggedValue::SameValue(result.GetTaggedValue(), expect.GetTaggedValue()), true);
    return JSTaggedValue::Undefined();
}

// native function for then then_on_rejected()
JSTaggedValue TestPromiseThenOnRejected(EcmaRuntimeCallInfo *argv)
{
    [[maybe_unused]] EcmaHandleScope handleScope(argv->GetThread());
    auto factory = argv->GetThread()->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> result = builtins_common::GetCallArg(argv, 0);
    auto expect = factory->NewFromCanBeCompressString("reject");
    EXPECT_EQ(JSTaggedValue::SameValue(result.GetTaggedValue(), expect.GetTaggedValue()), true);
    return JSTaggedValue::Undefined();
}

/*
 * @tc.name: Reject1
 * @tc.desc: The reject method receives a number.
 * @tc.type: FUNC
 */
TEST_F(BuiltinsPromiseTest, Reject1)
{
    JSHandle<GlobalEnv> env = EcmaVM::Cast(instance_)->GetGlobalEnv();

    JSHandle<JSFunction> promise = JSHandle<JSFunction>::Cast(env->GetPromiseFunction());
    JSHandle<JSTaggedValue> paramMsg(thread_, JSTaggedValue(3));

    auto ecmaRuntimeCallInfo1 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue(*promise), 6);
    ecmaRuntimeCallInfo1->SetFunction(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetThis(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetCallArg(0, paramMsg.GetTaggedValue());

    /// @tc.steps: var p1 = Promise.reject(3).
    [[maybe_unused]] auto prevReject = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo1.get());
    JSTaggedValue result = promise::Reject(ecmaRuntimeCallInfo1.get());
    JSHandle<JSPromise> rejectPromise(thread_, result);
    EXPECT_EQ(JSTaggedValue::SameValue(rejectPromise->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::REJECTED))),
              true);
    EXPECT_EQ(JSTaggedValue::SameValue(rejectPromise->GetPromiseResult(), JSTaggedValue(3)), true);
}

/*
 * @tc.name: Reject2
 * @tc.desc: The reject method receives a promise object.
 * @tc.type: FUNC
 */
TEST_F(BuiltinsPromiseTest, Reject2)
{
    ObjectFactory *factory = EcmaVM::Cast(instance_)->GetFactory();
    JSHandle<GlobalEnv> env = EcmaVM::Cast(instance_)->GetGlobalEnv();

    // constructor promise1
    JSHandle<JSFunction> promise = JSHandle<JSFunction>::Cast(env->GetPromiseFunction());
    JSHandle<JSTaggedValue> paramMsg1 =
        JSHandle<JSTaggedValue>::Cast(factory->NewFromCanBeCompressString("Promise reject"));

    /// @tc.steps: step1. var p1 = Promise.reject("Promise reject")
    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, promise.GetTaggedValue(), 6);
    ecmaRuntimeCallInfo->SetFunction(promise.GetTaggedValue());
    ecmaRuntimeCallInfo->SetThis(promise.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(0, paramMsg1.GetTaggedValue());

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = promise::Reject(ecmaRuntimeCallInfo.get());
    JSHandle<JSPromise> promise1(thread_, result);
    EXPECT_EQ(JSTaggedValue::SameValue(promise1->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::REJECTED))),
              true);
    EXPECT_EQ(JSTaggedValue::SameValue(promise1->GetPromiseResult(), paramMsg1.GetTaggedValue()), true);

    /// @tc.steps: step2. var p2 = Promise.reject(p1)
    auto ecmaRuntimeCallInfo1 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, promise.GetTaggedValue(), 6);
    ecmaRuntimeCallInfo1->SetFunction(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetThis(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetCallArg(0, promise1.GetTaggedValue());
    [[maybe_unused]] auto prev1 = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo1.get());

    JSTaggedValue result1 = promise::Reject(ecmaRuntimeCallInfo1.get());
    JSHandle<JSPromise> promise2(thread_, result1);
    EXPECT_NE(*promise1, *promise2);
    EXPECT_EQ(JSTaggedValue::SameValue(promise2->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::REJECTED))),
              true);
    EXPECT_EQ(
        JSTaggedValue::SameValue(promise2->GetPromiseResult(), JSTaggedValue(promise1.GetTaggedValue().GetRawData())),
        true);
}

/*
 * @tc.name: Resolve1
 * @tc.desc: The resolve method receives a number.
 * @tc.type: FUNC
 */
TEST_F(BuiltinsPromiseTest, Resolve1)
{
    JSHandle<GlobalEnv> env = EcmaVM::Cast(instance_)->GetGlobalEnv();

    JSHandle<JSFunction> promise = JSHandle<JSFunction>::Cast(env->GetPromiseFunction());
    JSHandle<JSTaggedValue> paramMsg(thread_, JSTaggedValue(5));

    /// @tc.steps: step1. var p1 = Promise.resolve(12345)
    auto ecmaRuntimeCallInfo1 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue(*promise), 6);
    ecmaRuntimeCallInfo1->SetFunction(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetThis(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetCallArg(0, paramMsg.GetTaggedValue());

    [[maybe_unused]] auto prevReject = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo1.get());
    JSTaggedValue result = promise::Resolve(ecmaRuntimeCallInfo1.get());
    JSHandle<JSPromise> rejectPromise(thread_, result);
    EXPECT_EQ(JSTaggedValue::SameValue(rejectPromise->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::FULFILLED))),
              true);
    EXPECT_EQ(JSTaggedValue::SameValue(rejectPromise->GetPromiseResult(), JSTaggedValue(5)), true);
}

/*
 * @tc.name: Resolve2
 * @tc.desc: The resolve method receives a promise object.
 * @tc.type: FUNC
 */
TEST_F(BuiltinsPromiseTest, Resolve2)
{
    ObjectFactory *factory = EcmaVM::Cast(instance_)->GetFactory();
    JSHandle<GlobalEnv> env = EcmaVM::Cast(instance_)->GetGlobalEnv();

    // constructor promise1
    JSHandle<JSFunction> promise = JSHandle<JSFunction>::Cast(env->GetPromiseFunction());
    JSHandle<JSTaggedValue> paramMsg1 =
        JSHandle<JSTaggedValue>::Cast(factory->NewFromCanBeCompressString("Promise reject"));

    /// @tc.steps: step1. var p1 = Promise.reject("Promise reject")
    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, promise.GetTaggedValue(), 6);
    ecmaRuntimeCallInfo->SetFunction(promise.GetTaggedValue());
    ecmaRuntimeCallInfo->SetThis(promise.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(0, paramMsg1.GetTaggedValue());

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = promise::Reject(ecmaRuntimeCallInfo.get());
    JSHandle<JSPromise> promise1(thread_, result);
    EXPECT_EQ(JSTaggedValue::SameValue(promise1->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::REJECTED))),
              true);
    EXPECT_EQ(JSTaggedValue::SameValue(promise1->GetPromiseResult(), paramMsg1.GetTaggedValue()), true);

    // promise1 Enter Reject() as a parameter.
    /// @tc.steps: step2. var p2 = Promise.resolve(p1)
    auto ecmaRuntimeCallInfo1 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, promise.GetTaggedValue(), 6);
    ecmaRuntimeCallInfo1->SetFunction(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetThis(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetCallArg(0, promise1.GetTaggedValue());
    [[maybe_unused]] auto prev1 = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo1.get());

    JSTaggedValue result1 = promise::Resolve(ecmaRuntimeCallInfo1.get());
    JSHandle<JSPromise> promise2(thread_, result1);
    EXPECT_EQ(*promise1, *promise2);
    EXPECT_EQ(JSTaggedValue::SameValue(promise2->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::REJECTED))),
              true);
    EXPECT_EQ(JSTaggedValue::SameValue(promise2->GetPromiseResult(), paramMsg1.GetTaggedValue()), true);
}

/*
 * @tc.name: Race1
 * @tc.desc: The race method receives an array.
 * @tc.type: FUNC
 */
TEST_F(BuiltinsPromiseTest, Race1)
{
    JSHandle<GlobalEnv> env = EcmaVM::Cast(instance_)->GetGlobalEnv();

    JSHandle<JSFunction> promise = JSHandle<JSFunction>::Cast(env->GetPromiseFunction());
    JSHandle<JSTaggedValue> paramMsg1(thread_, JSTaggedValue(12345));
    JSHandle<JSTaggedValue> paramMsg2(thread_, JSTaggedValue(6789));

    /// @tc.steps: step1. var p1 = Promise.reject(12345)
    auto ecmaRuntimeCallInfo1 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue(*promise), 6);
    ecmaRuntimeCallInfo1->SetFunction(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetThis(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetCallArg(0, paramMsg1.GetTaggedValue());

    [[maybe_unused]] auto prevReject = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo1.get());
    JSTaggedValue result1 = promise::Reject(ecmaRuntimeCallInfo1.get());
    JSHandle<JSPromise> rejectPromise(thread_, result1);
    EXPECT_EQ(JSTaggedValue::SameValue(rejectPromise->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::REJECTED))),
              true);
    EXPECT_EQ(JSTaggedValue::SameValue(rejectPromise->GetPromiseResult(), JSTaggedValue(12345)), true);

    /// @tc.steps: step2. var p2 = Promise.resolve(6789)
    auto ecmaRuntimeCallInfo2 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue(*promise), 6);
    ecmaRuntimeCallInfo2->SetFunction(promise.GetTaggedValue());
    ecmaRuntimeCallInfo2->SetThis(promise.GetTaggedValue());
    ecmaRuntimeCallInfo2->SetCallArg(0, paramMsg2.GetTaggedValue());

    [[maybe_unused]] auto prevResolve = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo2.get());
    JSTaggedValue result2 = promise::Resolve(ecmaRuntimeCallInfo2.get());
    JSHandle<JSPromise> resolvePromise(thread_, result2);
    EXPECT_EQ(JSTaggedValue::SameValue(resolvePromise->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::FULFILLED))),
              true);
    EXPECT_EQ(JSTaggedValue::SameValue(resolvePromise->GetPromiseResult(), JSTaggedValue(6789)), true);

    /// @tc.steps: step3. Construct an array with two elements p1 and p2. array = [p1. p2]
    JSHandle<JSObject> array(JSArray::ArrayCreate(thread_, JSTaggedNumber(2)));
    PropertyDescriptor desc(thread_, JSHandle<JSTaggedValue>::Cast(rejectPromise));
    JSArray::DefineOwnProperty(thread_, array, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(0)), desc);

    PropertyDescriptor desc1(thread_, JSHandle<JSTaggedValue>::Cast(resolvePromise));
    JSArray::DefineOwnProperty(thread_, array, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(1)), desc1);

    /// @tc.steps: step4. var p3 = Promise.race([p1,p2]);
    auto ecmaRuntimeCallInfo4 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue(*promise), 6);
    ecmaRuntimeCallInfo4->SetFunction(promise.GetTaggedValue());
    ecmaRuntimeCallInfo4->SetThis(promise.GetTaggedValue());
    ecmaRuntimeCallInfo4->SetCallArg(0, array.GetTaggedValue());

    [[maybe_unused]] auto prev4 = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo4.get());
    JSTaggedValue result4 = promise::Race(ecmaRuntimeCallInfo4.get());
    JSHandle<JSPromise> racePromise(thread_, result4);
    EXPECT_EQ(JSTaggedValue::SameValue(racePromise->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::PENDING))),
              true);
    EXPECT_EQ(racePromise->GetPromiseResult().IsUndefined(), true);
}

/*
 * @tc.name: Race2
 * @tc.desc: The Race method receives an array, uses the Then method to save the task in the task queue, and outputs
 * the execution result of the queue.
 * @tc.type: FUNC
 */
TEST_F(BuiltinsPromiseTest, Race2)
{
    ObjectFactory *factory = EcmaVM::Cast(instance_)->GetFactory();
    JSHandle<GlobalEnv> env = EcmaVM::Cast(instance_)->GetGlobalEnv();

    JSHandle<JSFunction> promise = JSHandle<JSFunction>::Cast(env->GetPromiseFunction());
    JSHandle<JSTaggedValue> paramMsg1(thread_, JSTaggedValue(12345));
    JSHandle<JSTaggedValue> paramMsg2(thread_, JSTaggedValue(6789));

    /// @tc.steps: step1. var p1 = Promise.reject(12345)
    auto ecmaRuntimeCallInfo1 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue(*promise), 6);
    ecmaRuntimeCallInfo1->SetFunction(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetThis(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetCallArg(0, paramMsg1.GetTaggedValue());

    [[maybe_unused]] auto prevReject = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo1.get());
    JSTaggedValue result1 = promise::Reject(ecmaRuntimeCallInfo1.get());
    JSHandle<JSPromise> rejectPromise(thread_, result1);
    EXPECT_EQ(JSTaggedValue::SameValue(rejectPromise->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::REJECTED))),
              true);
    EXPECT_EQ(JSTaggedValue::SameValue(rejectPromise->GetPromiseResult(), JSTaggedValue(12345)), true);

    /// @tc.steps: step2. var p2 = Promise.resolve(6789)
    auto ecmaRuntimeCallInfo2 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue(*promise), 6);
    ecmaRuntimeCallInfo2->SetFunction(promise.GetTaggedValue());
    ecmaRuntimeCallInfo2->SetThis(promise.GetTaggedValue());
    ecmaRuntimeCallInfo2->SetCallArg(0, paramMsg2.GetTaggedValue());

    [[maybe_unused]] auto prevResolve = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo2.get());
    JSTaggedValue result2 = promise::Resolve(ecmaRuntimeCallInfo2.get());
    JSHandle<JSPromise> resolvePromise(thread_, result2);
    EXPECT_EQ(JSTaggedValue::SameValue(resolvePromise->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::FULFILLED))),
              true);
    EXPECT_EQ(JSTaggedValue::SameValue(resolvePromise->GetPromiseResult(), JSTaggedValue(6789)), true);

    /// @tc.steps: step3. Construct an array with two elements p1 and p2. array = [p1. p2]
    JSHandle<JSObject> array(JSArray::ArrayCreate(thread_, JSTaggedNumber(2)));
    PropertyDescriptor desc(thread_, JSHandle<JSTaggedValue>::Cast(rejectPromise));
    JSArray::DefineOwnProperty(thread_, array, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(0)), desc);

    PropertyDescriptor desc1(thread_, JSHandle<JSTaggedValue>::Cast(resolvePromise));
    JSArray::DefineOwnProperty(thread_, array, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(1)), desc1);

    /// @tc.steps: step4. var p3 = Promise.race([p1,p2]);
    auto ecmaRuntimeCallInfo4 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue(*promise), 6);
    ecmaRuntimeCallInfo4->SetFunction(promise.GetTaggedValue());
    ecmaRuntimeCallInfo4->SetThis(promise.GetTaggedValue());
    ecmaRuntimeCallInfo4->SetCallArg(0, array.GetTaggedValue());

    [[maybe_unused]] auto prev4 = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo4.get());
    JSTaggedValue result4 = promise::Race(ecmaRuntimeCallInfo4.get());
    JSHandle<JSPromise> racePromise(thread_, result4);
    EXPECT_EQ(JSTaggedValue::SameValue(racePromise->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::PENDING))),
              true);
    EXPECT_EQ(racePromise->GetPromiseResult().IsUndefined(), true);

    /// @tc.steps: step5. p3.then((resolve)=>{print(resolve)}, (reject)=>{print(reject)})
    JSHandle<JSFunction> nativeFuncRaceThenOnrejected =
        factory->NewJSFunction(env, reinterpret_cast<void *>(TestPromiseRaceThenOnRejectd));
    auto ecmaRuntimeCallInfo5 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, racePromise.GetTaggedValue(), 8);
    ecmaRuntimeCallInfo5->SetFunction(racePromise.GetTaggedValue());
    ecmaRuntimeCallInfo5->SetThis(racePromise.GetTaggedValue());
    ecmaRuntimeCallInfo5->SetCallArg(0, JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo5->SetCallArg(1, nativeFuncRaceThenOnrejected.GetTaggedValue());

    [[maybe_unused]] auto prev5 = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo5.get());
    JSTaggedValue thenResult = promise::proto::Then(ecmaRuntimeCallInfo5.get());
    JSHandle<JSPromise> thenPromise(thread_, thenResult);

    EXPECT_TRUE(JSTaggedValue::SameValue(thenPromise->GetPromiseState(),
                                         JSTaggedValue(static_cast<int32_t>(PromiseStatus::PENDING))));
    EXPECT_TRUE(thenPromise->GetPromiseResult().IsUndefined());

    /// @tc.steps: step6. execute promise queue
    auto microJobQueue = EcmaVM::Cast(instance_)->GetMicroJobQueue();
    if (LIKELY(!thread_->HasPendingException())) {
        job::MicroJobQueue::ExecutePendingJob(thread_, microJobQueue);
    }
}

/*
 * @tc.name: All
 * @tc.desc: The All method receives an array, uses the Then method to save the task in the task queue, and outputs the
 * execution result of the queue.
 * @tc.type: FUNC
 */
TEST_F(BuiltinsPromiseTest, All)
{
    ObjectFactory *factory = EcmaVM::Cast(instance_)->GetFactory();
    JSHandle<GlobalEnv> env = EcmaVM::Cast(instance_)->GetGlobalEnv();

    JSHandle<JSFunction> promise = JSHandle<JSFunction>::Cast(env->GetPromiseFunction());
    JSHandle<JSTaggedValue> paramMsg1(thread_, JSTaggedValue(111));
    JSHandle<JSTaggedValue> paramMsg2(thread_, JSTaggedValue(222));

    /// @tc.steps: step1. var p1 = Promise.resolve(111)
    auto ecmaRuntimeCallInfo1 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue(*promise), 6);
    ecmaRuntimeCallInfo1->SetFunction(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetThis(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetCallArg(0, paramMsg1.GetTaggedValue());

    [[maybe_unused]] auto prevReject = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo1.get());
    JSTaggedValue result1 = promise::Resolve(ecmaRuntimeCallInfo1.get());
    JSHandle<JSPromise> resolvePromise1(thread_, result1);
    EXPECT_EQ(JSTaggedValue::SameValue(resolvePromise1->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::FULFILLED))),
              true);
    EXPECT_EQ(JSTaggedValue::SameValue(resolvePromise1->GetPromiseResult(), JSTaggedValue(111)), true);

    /// @tc.steps: step2. var p2 = Promise.resolve(222)
    auto ecmaRuntimeCallInfo2 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue(*promise), 6);
    ecmaRuntimeCallInfo2->SetFunction(promise.GetTaggedValue());
    ecmaRuntimeCallInfo2->SetThis(promise.GetTaggedValue());
    ecmaRuntimeCallInfo2->SetCallArg(0, paramMsg2.GetTaggedValue());

    [[maybe_unused]] auto prevResolve = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo2.get());
    JSTaggedValue result2 = promise::Resolve(ecmaRuntimeCallInfo2.get());
    JSHandle<JSPromise> resolvePromise2(thread_, result2);
    EXPECT_EQ(JSTaggedValue::SameValue(resolvePromise2->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::FULFILLED))),
              true);
    EXPECT_EQ(JSTaggedValue::SameValue(resolvePromise2->GetPromiseResult(), JSTaggedValue(222)), true);

    /// @tc.steps: step3. Construct an array with two elements p1 and p2. array = [p1. p2]
    JSHandle<JSObject> array(JSArray::ArrayCreate(thread_, JSTaggedNumber(2)));
    PropertyDescriptor desc(thread_, JSHandle<JSTaggedValue>::Cast(resolvePromise1));
    JSArray::DefineOwnProperty(thread_, array, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(0)), desc);

    PropertyDescriptor desc1(thread_, JSHandle<JSTaggedValue>::Cast(resolvePromise2));
    JSArray::DefineOwnProperty(thread_, array, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(1)), desc1);

    /// @tc.steps: step4. var p3 = Promise.all([p1,p2]);
    auto ecmaRuntimeCallInfo4 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue(*promise), 6);
    ecmaRuntimeCallInfo4->SetFunction(promise.GetTaggedValue());
    ecmaRuntimeCallInfo4->SetThis(promise.GetTaggedValue());
    ecmaRuntimeCallInfo4->SetCallArg(0, array.GetTaggedValue());

    [[maybe_unused]] auto prev4 = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo4.get());
    JSTaggedValue result4 = promise::All(ecmaRuntimeCallInfo4.get());
    JSHandle<JSPromise> allPromise(thread_, result4);
    EXPECT_EQ(JSTaggedValue::SameValue(allPromise->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::PENDING))),
              true);
    EXPECT_EQ(allPromise->GetPromiseResult().IsUndefined(), true);

    /// @tc.steps: step5. p3.then((resolve)=>{print(resolve)}, (reject)=>{print(reject)});
    JSHandle<JSFunction> nativeFuncRaceThenOnResolved =
        factory->NewJSFunction(env, reinterpret_cast<void *>(TestPromiseAllThenOnResolved));
    auto ecmaRuntimeCallInfo5 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, allPromise.GetTaggedValue(), 8);
    ecmaRuntimeCallInfo5->SetFunction(allPromise.GetTaggedValue());
    ecmaRuntimeCallInfo5->SetThis(allPromise.GetTaggedValue());
    ecmaRuntimeCallInfo5->SetCallArg(0, nativeFuncRaceThenOnResolved.GetTaggedValue());
    ecmaRuntimeCallInfo5->SetCallArg(1, nativeFuncRaceThenOnResolved.GetTaggedValue());

    [[maybe_unused]] auto prev5 = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo5.get());
    JSTaggedValue thenResult = promise::proto::Then(ecmaRuntimeCallInfo5.get());
    JSHandle<JSPromise> thenPromise(thread_, thenResult);

    EXPECT_TRUE(JSTaggedValue::SameValue(thenPromise->GetPromiseState(),
                                         JSTaggedValue(static_cast<int32_t>(PromiseStatus::PENDING))));
    EXPECT_TRUE(thenPromise->GetPromiseResult().IsUndefined());

    /// @tc.steps: step6. execute promise queue
    auto microJobQueue = EcmaVM::Cast(instance_)->GetMicroJobQueue();
    if (LIKELY(!thread_->HasPendingException())) {
        job::MicroJobQueue::ExecutePendingJob(thread_, microJobQueue);
    }
}

/*
 * @tc.name: Catch
 * @tc.desc: test Catch() method
 * @tc.type: FUNC
 */
TEST_F(BuiltinsPromiseTest, Catch)
{
    auto env = EcmaVM::Cast(instance_)->GetGlobalEnv();
    auto factory = EcmaVM::Cast(instance_)->GetFactory();

    JSHandle<JSFunction> promise = JSHandle<JSFunction>::Cast(env->GetPromiseFunction());
    JSHandle<JSTaggedValue> paramMsg1(thread_, JSTaggedValue(3));

    /// @tc.steps: step1. var p1 = Promise.reject(3)
    auto ecmaRuntimeCallInfo1 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue(*promise), 6);
    ecmaRuntimeCallInfo1->SetFunction(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetThis(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetCallArg(0, paramMsg1.GetTaggedValue());

    [[maybe_unused]] auto prevReject = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo1.get());
    JSTaggedValue result = promise::Reject(ecmaRuntimeCallInfo1.get());
    JSHandle<JSPromise> rejectPromise(thread_, result);
    EXPECT_EQ(JSTaggedValue::SameValue(rejectPromise->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::REJECTED))),
              true);
    EXPECT_EQ(JSTaggedValue::SameValue(rejectPromise->GetPromiseResult(), JSTaggedValue(3)), true);

    /// @tc.steps: step2. p1 invokes catch()
    JSHandle<JSFunction> testPromiseCatch = factory->NewJSFunction(env, reinterpret_cast<void *>(TestPromiseCatch));
    auto ecmaRuntimeCallInfo2 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, rejectPromise.GetTaggedValue(), 6);
    ecmaRuntimeCallInfo2->SetFunction(rejectPromise.GetTaggedValue());
    ecmaRuntimeCallInfo2->SetThis(rejectPromise.GetTaggedValue());
    ecmaRuntimeCallInfo2->SetCallArg(0, testPromiseCatch.GetTaggedValue());

    [[maybe_unused]] auto prevCatch = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo2.get());
    JSTaggedValue catchResult = promise::proto::Catch(ecmaRuntimeCallInfo2.get());
    JSHandle<JSPromise> catchPromise(thread_, catchResult);

    EXPECT_EQ(JSTaggedValue::SameValue(catchPromise->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::PENDING))),
              true);
    EXPECT_EQ(catchPromise->GetPromiseResult().IsUndefined(), true);

    /// @tc.steps: step3. execute promise queue
    auto microJobQueue = EcmaVM::Cast(instance_)->GetMicroJobQueue();
    if (LIKELY(!thread_->HasPendingException())) {
        job::MicroJobQueue::ExecutePendingJob(thread_, microJobQueue);
    }
}

/*
 * @tc.name: ThenResolve
 * @tc.desc: Testing the Then() function with the Resolve() function
 * @tc.type: FUNC
 */
TEST_F(BuiltinsPromiseTest, ThenResolve)
{
    auto env = EcmaVM::Cast(instance_)->GetGlobalEnv();
    auto factory = EcmaVM::Cast(instance_)->GetFactory();

    JSHandle<JSFunction> promise = JSHandle<JSFunction>::Cast(env->GetPromiseFunction());
    JSHandle<JSTaggedValue> paramMsg = JSHandle<JSTaggedValue>::Cast(factory->NewFromCanBeCompressString("resolve"));

    /// @tc.steps: step1. var p1 = Promise.resolve("resolve")
    auto ecmaRuntimeCallInfo1 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue(*promise), 6);
    ecmaRuntimeCallInfo1->SetFunction(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetThis(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetCallArg(0, paramMsg.GetTaggedValue());

    [[maybe_unused]] auto prevReject = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo1.get());
    JSTaggedValue result = promise::Resolve(ecmaRuntimeCallInfo1.get());
    JSHandle<JSPromise> resolvePromise(thread_, result);
    EXPECT_EQ(JSTaggedValue::SameValue(resolvePromise->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::FULFILLED))),
              true);
    EXPECT_EQ(JSTaggedValue::SameValue(resolvePromise->GetPromiseResult(), paramMsg.GetTaggedValue()), true);

    /// @tc.steps: step2. p1 invokes then()
    JSHandle<JSFunction> testPromiseThenOnResolved =
        factory->NewJSFunction(env, reinterpret_cast<void *>(TestPromiseThenOnResolved));
    auto ecmaRuntimeCallInfo2 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, resolvePromise.GetTaggedValue(), 8);
    ecmaRuntimeCallInfo2->SetFunction(resolvePromise.GetTaggedValue());
    ecmaRuntimeCallInfo2->SetThis(resolvePromise.GetTaggedValue());
    ecmaRuntimeCallInfo2->SetCallArg(0, testPromiseThenOnResolved.GetTaggedValue());
    ecmaRuntimeCallInfo2->SetCallArg(1, JSTaggedValue::Undefined());

    [[maybe_unused]] auto prevThen = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo2.get());
    JSTaggedValue thenResult = promise::proto::Then(ecmaRuntimeCallInfo2.get());
    JSHandle<JSPromise> thenPromise(thread_, thenResult);

    EXPECT_EQ(JSTaggedValue::SameValue(thenPromise->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::PENDING))),
              true);
    EXPECT_EQ(thenPromise->GetPromiseResult().IsUndefined(), true);

    /// @tc.steps: step3.  execute promise queue
    auto microJobQueue = EcmaVM::Cast(instance_)->GetMicroJobQueue();
    if (LIKELY(!thread_->HasPendingException())) {
        job::MicroJobQueue::ExecutePendingJob(thread_, microJobQueue);
    }
}

/*
 * @tc.name: ThenReject
 * @tc.desc: Testing the Then() function with the Reject() function
 * @tc.type: FUNC
 */
TEST_F(BuiltinsPromiseTest, ThenReject)
{
    auto env = EcmaVM::Cast(instance_)->GetGlobalEnv();
    auto factory = EcmaVM::Cast(instance_)->GetFactory();

    JSHandle<JSFunction> promise = JSHandle<JSFunction>::Cast(env->GetPromiseFunction());
    JSHandle<JSTaggedValue> paramMsg = JSHandle<JSTaggedValue>::Cast(factory->NewFromCanBeCompressString("reject"));

    /// @tc.steps: step1. var p1 = Promise.Reject(5)
    auto ecmaRuntimeCallInfo1 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue(*promise), 6);
    ecmaRuntimeCallInfo1->SetFunction(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetThis(promise.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetCallArg(0, paramMsg.GetTaggedValue());

    [[maybe_unused]] auto prevReject = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo1.get());
    JSTaggedValue result = promise::Reject(ecmaRuntimeCallInfo1.get());
    JSHandle<JSPromise> rejectPromise(thread_, result);
    EXPECT_EQ(JSTaggedValue::SameValue(rejectPromise->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::REJECTED))),
              true);
    EXPECT_EQ(JSTaggedValue::SameValue(rejectPromise->GetPromiseResult(), paramMsg.GetTaggedValue()), true);

    /// @tc.steps: step1. p1 invokes then()
    JSHandle<JSFunction> testPromiseThenOnRejected =
        factory->NewJSFunction(env, reinterpret_cast<void *>(TestPromiseThenOnRejected));
    auto ecmaRuntimeCallInfo2 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, rejectPromise.GetTaggedValue(), 8);
    ecmaRuntimeCallInfo2->SetFunction(rejectPromise.GetTaggedValue());
    ecmaRuntimeCallInfo2->SetThis(rejectPromise.GetTaggedValue());
    ecmaRuntimeCallInfo2->SetCallArg(0, testPromiseThenOnRejected.GetTaggedValue());
    ecmaRuntimeCallInfo2->SetCallArg(1, testPromiseThenOnRejected.GetTaggedValue());

    [[maybe_unused]] auto prevThen = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo2.get());
    JSTaggedValue thenResult = promise::proto::Then(ecmaRuntimeCallInfo2.get());
    JSHandle<JSPromise> thenPromise(thread_, thenResult);

    EXPECT_EQ(JSTaggedValue::SameValue(thenPromise->GetPromiseState(),
                                       JSTaggedValue(static_cast<int32_t>(PromiseStatus::PENDING))),
              true);
    EXPECT_EQ(thenPromise->GetPromiseResult().IsUndefined(), true);
    /// @tc.steps: step3.  execute promise queue
    auto microJobQueue = EcmaVM::Cast(instance_)->GetMicroJobQueue();
    if (LIKELY(!thread_->HasPendingException())) {
        job::MicroJobQueue::ExecutePendingJob(thread_, microJobQueue);
    }
}
}  // namespace ark::test

// NOLINTEND(readability-magic-numbers)
